/*
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * This software is open source.
 * See the bottom of this file for the licence.
 */

package com.googlecode.bluetools.dom4j.io;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.DefaultHandler2;

import com.googlecode.bluetools.dom4j.Attribute;
import com.googlecode.bluetools.dom4j.Branch;
import com.googlecode.bluetools.dom4j.Comment;
import com.googlecode.bluetools.dom4j.Document;
import com.googlecode.bluetools.dom4j.DocumentFactory;
import com.googlecode.bluetools.dom4j.DocumentType;
import com.googlecode.bluetools.dom4j.Element;
import com.googlecode.bluetools.dom4j.ElementHandler;
import com.googlecode.bluetools.dom4j.Entity;
import com.googlecode.bluetools.dom4j.Location;
import com.googlecode.bluetools.dom4j.Namespace;
import com.googlecode.bluetools.dom4j.ProcessingInstruction;
import com.googlecode.bluetools.dom4j.QName;
import com.googlecode.bluetools.dom4j.Text;
import com.googlecode.bluetools.dom4j.dtd.AttributeDecl;
import com.googlecode.bluetools.dom4j.dtd.ElementDecl;
import com.googlecode.bluetools.dom4j.dtd.ExternalEntityDecl;
import com.googlecode.bluetools.dom4j.dtd.InternalEntityDecl;
import com.googlecode.bluetools.dom4j.tree.AbstractElement;
import com.googlecode.bluetools.dom4j.tree.NamespaceStack;

/**
 * <p>
 * <code>SAXContentHandler</code> builds a dom4j tree via SAX events.
 * </p>
 * 
 * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
 * @version $Revision: 1.2 $
 */
public class SAXContentHandler extends DefaultHandler2 {

	/** The factory used to create new <code>Document</code> instances */
	private DocumentFactory documentFactory;

	/** The document that is being built */
	private Document document;

	/** stack of <code>Element</code> objects */
	private ElementStack elementStack;

	/** stack of <code>Namespace</code> and <code>QName</code> objects */
	private NamespaceStack namespaceStack;

	/** the <code>ElementHandler</code> called as the elements are complete */
	private ElementHandler elementHandler;

	/** the Locator */
	protected Locator locator;

	/** The name of the current entity */
	private String entity;

	/** Flag used to indicate that we are inside a DTD section */
	private boolean insideDTDSection;

	/** Flag used to indicate that we are inside a CDATA section */
	private boolean insideCDATASection;

	/**
	 * buffer to hold contents of cdata section across multiple characters events
	 */
	private StringBuffer cdataText;

	/** namespaces that are available for use */
	private Map availableNamespaceMap = new HashMap();

	/** declared namespaces that are not yet available for use */
	private List declaredNamespaceList = new ArrayList();

	/** internal DTD declarations */
	private List internalDTDDeclarations;

	/** external DTD declarations */
	private List externalDTDDeclarations;

	/** The number of namespaces that are declared in the current scope */
	private int declaredNamespaceIndex;

	/** The entity resolver */
	private EntityResolver entityResolver;

	private InputSource inputSource;

	/** The current element we are on */
	private Element currentElement;

	/** Should internal DTD declarations be expanded into a List in the DTD */
	private boolean includeInternalDTDDeclarations = false;

	/** Should external DTD declarations be expanded into a List in the DTD */
	private boolean includeExternalDTDDeclarations = false;

	/** The number of levels deep we are inside a startEntity/endEntity call */
	private int entityLevel;

	/** Are we in an internal DTD subset? */
	private boolean internalDTDsubset = false;

	/** Whether adjacent text nodes should be merged */
	private boolean mergeAdjacentText = false;

	/** Have we added text to the buffer */
	private boolean textInTextBuffer = false;

	/** Should we ignore comments */
	private boolean ignoreComments = false;

	/** Buffer used to concatenate text together */
	private StringBuffer textBuffer;

	/** Holds value of property stripWhitespaceText. */
	private boolean stripWhitespaceText = false;

	public SAXContentHandler() {
		this(DocumentFactory.getInstance());
	}

	public SAXContentHandler(DocumentFactory documentFactory) {
		this(documentFactory, null);
	}

	public SAXContentHandler(DocumentFactory documentFactory, ElementHandler elementHandler) {
		this(documentFactory, elementHandler, null);
		this.elementStack = createElementStack();
	}

	public SAXContentHandler(DocumentFactory documentFactory, ElementHandler elementHandler, ElementStack elementStack) {
		this.documentFactory = documentFactory;
		this.elementHandler = elementHandler;
		this.elementStack = elementStack;
		this.namespaceStack = new NamespaceStack(documentFactory);
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @return the document that has been or is being built
	 */
	public Document getDocument() {
		if (document == null) {
			document = createDocument();
		}

		return document;
	}

	// ContentHandler interface
	// -------------------------------------------------------------------------
	public void setDocumentLocator(Locator documentLocator) {
		this.locator = documentLocator;
	}

	public void processingInstruction(String target, String data) throws SAXException {
		if (mergeAdjacentText && textInTextBuffer) {
			completeCurrentTextNode();
		}

		ProcessingInstruction pi = null;
		if (currentElement != null) {
			pi = currentElement.addProcessingInstruction(target, data);
		}
		else {
			pi = getDocument().addProcessingInstruction(target, data);
		}
		if (pi != null) {
			sestProcessingInstructionLocation(pi);
		}
	}

	public void startPrefixMapping(String prefix, String uri) throws SAXException {
		namespaceStack.push(prefix, uri);
	}

	public void endPrefixMapping(String prefix) throws SAXException {
		namespaceStack.pop(prefix);
		declaredNamespaceIndex = namespaceStack.size();
	}

	public void startDocument() throws SAXException {
		// document = createDocument();
		document = null;
		currentElement = null;

		elementStack.clear();

		if ((elementHandler != null) && (elementHandler instanceof DispatchHandler)) {
			elementStack.setDispatchHandler((DispatchHandler) elementHandler);
		}

		namespaceStack.clear();
		declaredNamespaceIndex = 0;

		if (mergeAdjacentText && (textBuffer == null)) {
			textBuffer = new StringBuffer();
		}

		textInTextBuffer = false;

		setDocumentStartLocation(getDocument());
	}

	public void endDocument() throws SAXException {
		setDocumentEndLocation(getDocument());

		namespaceStack.clear();
		elementStack.clear();
		currentElement = null;
		textBuffer = null;
	}

	public void startElement(String namespaceURI, String localName, String qualifiedName, Attributes attributes)
			throws SAXException {
		if (mergeAdjacentText && textInTextBuffer) {
			completeCurrentTextNode();
		}

		QName qName = namespaceStack.getQName(namespaceURI, localName, qualifiedName);

		Branch branch = currentElement;

		if (branch == null) {
			branch = getDocument();
		}

		Element element = branch.addElement(qName);
		setElementStartLocation(element);

		// add all declared namespaces
		addDeclaredNamespaces(element);

		// now lets add all attribute values
		addAttributes(element, attributes);

		elementStack.pushElement(element);
		currentElement = element;

		entity = null; // fixes bug527062

		if (elementHandler != null) {
			elementHandler.onStart(elementStack);
		}
	}

	public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
		if (currentElement != null) {
			currentElement.setEndLocation(Location.newInstance(locator));
		}

		if (mergeAdjacentText && textInTextBuffer) {
			completeCurrentTextNode();
		}

		if ((elementHandler != null) && (currentElement != null)) {
			elementHandler.onEnd(elementStack);
		}

		elementStack.popElement();
		currentElement = elementStack.peekElement();
	}

	public void characters(char[] ch, int start, int end) throws SAXException {
		if (end == 0) {
			return;
		}

		if (currentElement != null) {
			if (entity != null) {
				if (mergeAdjacentText && textInTextBuffer) {
					completeCurrentTextNode();
				}

				Entity ent = currentElement.addEntity(entity, new String(ch, start, end));
				setEntityLocation(ent);

				entity = null;
			}
			else if (insideCDATASection) {
				if (mergeAdjacentText && textInTextBuffer) {
					completeCurrentTextNode();
				}

				cdataText.append(new String(ch, start, end));
			}
			else {
				if (mergeAdjacentText) {
					textBuffer.append(ch, start, end);
					textInTextBuffer = true;
				}
				else {
					Text txt = currentElement.addText(new String(ch, start, end));
					setTextLocation(txt);
				}
			}
		}
	}

	// ErrorHandler interface
	// -------------------------------------------------------------------------

	/**
	 * This method is called when a warning occurs during the parsing of the document. This method does nothing.
	 * 
	 * @param exception DOCUMENT ME!
	 * 
	 * @throws SAXException DOCUMENT ME!
	 */
	public void warning(SAXParseException exception) throws SAXException {
		// ignore warnings by default
	}

	/**
	 * This method is called when an error is detected during parsing such as a validation error. This method rethrows
	 * the exception
	 * 
	 * @param exception DOCUMENT ME!
	 * 
	 * @throws SAXException DOCUMENT ME!
	 */
	public void error(SAXParseException exception) throws SAXException {
		throw exception;
	}

	/**
	 * This method is called when a fatal error occurs during parsing. This method rethrows the exception
	 * 
	 * @param exception DOCUMENT ME!
	 * 
	 * @throws SAXException DOCUMENT ME!
	 */
	public void fatalError(SAXParseException exception) throws SAXException {
		throw exception;
	}

	// LexicalHandler interface
	// -------------------------------------------------------------------------
	public void startDTD(String name, String publicId, String systemId) throws SAXException {
		getDocument().addDocType(name, publicId, systemId);
		insideDTDSection = true;
		internalDTDsubset = true;

		setDTDStartLocation(getDocument().getDocType());
	}

	public void endDTD() throws SAXException {
		insideDTDSection = false;

		DocumentType docType = getDocument().getDocType();
		setDTDEndLocation(docType);

		if (docType != null) {
			if (internalDTDDeclarations != null) {
				docType.setInternalDeclarations(internalDTDDeclarations);
			}

			if (externalDTDDeclarations != null) {
				docType.setExternalDeclarations(externalDTDDeclarations);
			}
		}

		internalDTDDeclarations = null;
		externalDTDDeclarations = null;
	}

	public void startEntity(String name) throws SAXException {
		++entityLevel;

		// Ignore DTD references
		entity = null;

		if (!insideDTDSection) {
			if (!isIgnorableEntity(name)) {
				entity = name;
			}
		}

		// internal DTD subsets can only appear outside of a
		// startEntity/endEntity block
		// see the startDTD method in
		// http://dom4j.org/javadoc/org/xml/sax/ext/LexicalHandler.html
		internalDTDsubset = false;
	}

	public void endEntity(String name) throws SAXException {
		--entityLevel;
		entity = null;

		if (entityLevel == 0) {
			internalDTDsubset = true;
		}
	}

	public void startCDATA() throws SAXException {
		insideCDATASection = true;
		cdataText = new StringBuffer();
	}

	public void endCDATA() throws SAXException {
		insideCDATASection = false;
		currentElement.addCDATA(cdataText.toString());
	}

	public void comment(char[] ch, int start, int end) throws SAXException {
		if (!ignoreComments) {
			if (mergeAdjacentText && textInTextBuffer) {
				completeCurrentTextNode();
			}

			String text = new String(ch, start, end);

			Comment comment = null;
			if (!insideDTDSection && (text.length() > 0)) {
				if (currentElement != null) {
					comment = currentElement.addComment(text);
				}
				else {
					comment = getDocument().addComment(text);
				}
			}
			if (comment != null) {
				setCommentLocation(comment);
			}
		}
	}

	// DeclHandler interface
	// -------------------------------------------------------------------------

	/**
	 * Report an element type declaration.
	 * 
	 * <p>
	 * The content model will consist of the string "EMPTY", the string "ANY", or a parenthesised group, optionally
	 * followed by an occurrence indicator. The model will be normalized so that all parameter entities are fully
	 * resolved and all whitespace is removed,and will include the enclosing parentheses. Other normalization (such as
	 * removing redundant parentheses or simplifying occurrence indicators) is at the discretion of the parser.
	 * </p>
	 * 
	 * @param name The element type name.
	 * @param model The content model as a normalized string.
	 * 
	 * @exception SAXException The application may raise an exception.
	 */
	public void elementDecl(String name, String model) throws SAXException {
		ElementDecl elementDecl = new ElementDecl(name, model);
		setElementDeclLocation(elementDecl);

		if (internalDTDsubset) {
			if (includeInternalDTDDeclarations) {
				addDTDDeclaration(elementDecl);
			}
		}
		else {
			if (includeExternalDTDDeclarations) {
				addExternalDTDDeclaration(elementDecl);
			}
		}
	}

	/**
	 * Report an attribute type declaration.
	 * 
	 * <p>
	 * Only the effective (first) declaration for an attribute will be reported. The type will be one of the strings
	 * "CDATA", "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES", a parenthesized token group with
	 * the separator "|" and all whitespace removed, or the word "NOTATION" followed by a space followed by a
	 * parenthesized token group with all whitespace removed.
	 * </p>
	 * 
	 * <p>
	 * Any parameter entities in the attribute value will be expanded, but general entities will not.
	 * </p>
	 * 
	 * @param eName The name of the associated element.
	 * @param aName The name of the attribute.
	 * @param type A string representing the attribute type.
	 * @param valueDefault A string representing the attribute default ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
	 *            none of these applies.
	 * @param val A string representing the attribute's default value, or null if there is none.
	 * 
	 * @exception SAXException The application may raise an exception.
	 */
	public void attributeDecl(String eName, String aName, String type, String valueDefault, String val)
			throws SAXException {
		AttributeDecl attributeDecl = new AttributeDecl(eName, aName, type, valueDefault, val);

		if (internalDTDsubset) {
			if (includeInternalDTDDeclarations) {
				addDTDDeclaration(attributeDecl);
			}
		}
		else {
			if (includeExternalDTDDeclarations) {
				addExternalDTDDeclaration(attributeDecl);
			}
		}
	}

	/**
	 * Report an internal entity declaration.
	 * 
	 * <p>
	 * Only the effective (first) declaration for each entity will be reported. All parameter entities in the value will
	 * be expanded, but general entities will not.
	 * </p>
	 * 
	 * @param name The name of the entity. If it is a parameter entity, the name will begin with '%'.
	 * @param value The replacement text of the entity.
	 * 
	 * @exception SAXException The application may raise an exception.
	 * 
	 * @see #externalEntityDecl
	 * @see org.xml.sax.DTDHandler#unparsedEntityDecl
	 */
	public void internalEntityDecl(String name, String value) throws SAXException {
		InternalEntityDecl entityDecl = new InternalEntityDecl(name, value);
		if (internalDTDsubset) {
			if (includeInternalDTDDeclarations) {
				setInternalEntityDeclLocation(entityDecl);
				addDTDDeclaration(entityDecl);
			}
		}
		else {
			if (includeExternalDTDDeclarations) {
				addExternalDTDDeclaration(entityDecl);
			}
		}
	}

	/**
	 * Report a parsed external entity declaration.
	 * 
	 * <p>
	 * Only the effective (first) declaration for each entity will be reported.
	 * </p>
	 * 
	 * @param name The name of the entity. If it is a parameter entity, the name will begin with '%'.
	 * @param publicId The declared public identifier of the entity, or null if none was declared.
	 * @param sysId The declared system identifier of the entity.
	 * 
	 * @exception SAXException The application may raise an exception.
	 * 
	 * @see #internalEntityDecl
	 * @see org.xml.sax.DTDHandler#unparsedEntityDecl
	 */
	public void externalEntityDecl(String name, String publicId, String sysId) throws SAXException {
		ExternalEntityDecl declaration = new ExternalEntityDecl(name, publicId, sysId);
		setExternalEntityDeclLocation(declaration);

		if (internalDTDsubset) {
			if (includeInternalDTDDeclarations) {
				addDTDDeclaration(declaration);
			}
		}
		else {
			if (includeExternalDTDDeclarations) {
				addExternalDTDDeclaration(declaration);
			}
		}
	}

	// DTDHandler interface
	// -------------------------------------------------------------------------

	/**
	 * Receive notification of a notation declaration event.
	 * 
	 * <p>
	 * It is up to the application to record the notation for later reference, if necessary.
	 * </p>
	 * 
	 * <p>
	 * At least one of publicId and systemId must be non-null. If a system identifier is present, and it is a URL, the
	 * SAX parser must resolve it fully before passing it to the application through this event.
	 * </p>
	 * 
	 * <p>
	 * There is no guarantee that the notation declaration will be reported before any unparsed entities that use it.
	 * </p>
	 * 
	 * @param name The notation name.
	 * @param publicId The notation's public identifier, or null if none was given.
	 * @param systemId The notation's system identifier, or null if none was given.
	 * 
	 * @exception SAXException Any SAX exception, possibly wrapping another exception.
	 * 
	 * @see #unparsedEntityDecl
	 * @see org.xml.sax.AttributeList
	 */
	public void notationDecl(String name, String publicId, String systemId) throws SAXException {
		// #### not supported yet!
	}

	/**
	 * Receive notification of an unparsed entity declaration event.
	 * 
	 * <p>
	 * Note that the notation name corresponds to a notation reported by the {@link #notationDecl notationDecl}event. It
	 * is up to the application to record the entity for later reference, if necessary.
	 * </p>
	 * 
	 * <p>
	 * If the system identifier is a URL, the parser must resolve it fully before passing it to the application.
	 * </p>
	 * 
	 * @param name The unparsed entity's name.
	 * @param publicId The entity's public identifier, or null if none was given.
	 * @param systemId The entity's system identifier.
	 * @param notationName The name of the associated notation.
	 * 
	 * @exception SAXException Any SAX exception, possibly wrapping another exception.
	 * 
	 * @see #notationDecl
	 * @see org.xml.sax.AttributeList
	 */
	public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName)
			throws SAXException {
		// #### not supported yet!
	}

	// Properties
	// -------------------------------------------------------------------------
	public ElementStack getElementStack() {
		return elementStack;
	}

	public void setElementStack(ElementStack elementStack) {
		this.elementStack = elementStack;
	}

	public EntityResolver getEntityResolver() {
		return entityResolver;
	}

	public void setEntityResolver(EntityResolver entityResolver) {
		this.entityResolver = entityResolver;
	}

	public InputSource getInputSource() {
		return inputSource;
	}

	public void setInputSource(InputSource inputSource) {
		this.inputSource = inputSource;
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @return whether internal DTD declarations should be expanded into the DocumentType object or not.
	 */
	public boolean isIncludeInternalDTDDeclarations() {
		return includeInternalDTDDeclarations;
	}

	/**
	 * Sets whether internal DTD declarations should be expanded into the DocumentType object or not.
	 * 
	 * @param include whether or not DTD declarations should be expanded and included into the DocumentType object.
	 */
	public void setIncludeInternalDTDDeclarations(boolean include) {
		this.includeInternalDTDDeclarations = include;
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @return whether external DTD declarations should be expanded into the DocumentType object or not.
	 */
	public boolean isIncludeExternalDTDDeclarations() {
		return includeExternalDTDDeclarations;
	}

	/**
	 * Sets whether DTD external declarations should be expanded into the DocumentType object or not.
	 * 
	 * @param include whether or not DTD declarations should be expanded and included into the DocumentType object.
	 */
	public void setIncludeExternalDTDDeclarations(boolean include) {
		this.includeExternalDTDDeclarations = include;
	}

	/**
	 * Returns whether adjacent text nodes should be merged together.
	 * 
	 * @return Value of property mergeAdjacentText.
	 */
	public boolean isMergeAdjacentText() {
		return mergeAdjacentText;
	}

	/**
	 * Sets whether or not adjacent text nodes should be merged together when parsing.
	 * 
	 * @param mergeAdjacentText New value of property mergeAdjacentText.
	 */
	public void setMergeAdjacentText(boolean mergeAdjacentText) {
		this.mergeAdjacentText = mergeAdjacentText;
	}

	/**
	 * Sets whether whitespace between element start and end tags should be ignored
	 * 
	 * @return Value of property stripWhitespaceText.
	 */
	public boolean isStripWhitespaceText() {
		return stripWhitespaceText;
	}

	/**
	 * Sets whether whitespace between element start and end tags should be ignored.
	 * 
	 * @param stripWhitespaceText New value of property stripWhitespaceText.
	 */
	public void setStripWhitespaceText(boolean stripWhitespaceText) {
		this.stripWhitespaceText = stripWhitespaceText;
	}

	/**
	 * Returns whether we should ignore comments or not.
	 * 
	 * @return boolean
	 */
	public boolean isIgnoreComments() {
		return ignoreComments;
	}

	/**
	 * Sets whether we should ignore comments or not.
	 * 
	 * @param ignoreComments whether we should ignore comments or not.
	 */
	public void setIgnoreComments(boolean ignoreComments) {
		this.ignoreComments = ignoreComments;
	}

	// Implementation methods
	// -------------------------------------------------------------------------

	/**
	 * If the current text buffer contains any text then create a new text node with it and add it to the current
	 * element
	 */
	protected void completeCurrentTextNode() {
		if (stripWhitespaceText) {
			boolean whitespace = true;

			for (int i = 0, size = textBuffer.length(); i < size; i++) {
				if (!Character.isWhitespace(textBuffer.charAt(i))) {
					whitespace = false;

					break;
				}
			}

			if (!whitespace) {
				currentElement.addText(textBuffer.toString());
			}
		}
		else {
			currentElement.addText(textBuffer.toString());
		}

		textBuffer.setLength(0);
		textInTextBuffer = false;
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @return the current document
	 */
	protected Document createDocument() {
		String encoding = getEncoding();
		Document doc = documentFactory.createDocument(encoding);

		// set the EntityResolver
		doc.setEntityResolver(entityResolver);

		if (inputSource != null) {
			doc.setName(inputSource.getSystemId());
		}

		return doc;
	}

	private String getEncoding() {
		if (locator == null) {
			return null;
		}

		// use reflection to avoid dependency on Locator2
		// or other locator implemenations.
		try {
			Method m = locator.getClass().getMethod("getEncoding", new Class[] {});

			if (m != null) {
				return (String) m.invoke(locator, null);
			}
		}
		catch (Exception e) {
			// do nothing
		}

		// couldn't determine encoding, returning null...
		return null;
	}

	/**
	 * a Strategy Method to determine if a given entity name is ignorable
	 * 
	 * @param name DOCUMENT ME!
	 * 
	 * @return DOCUMENT ME!
	 */
	protected boolean isIgnorableEntity(String name) {
		return "amp".equals(name) || "apos".equals(name) || "gt".equals(name) || "lt".equals(name)
				|| "quot".equals(name);
	}

	/**
	 * Add all namespaces declared before the startElement() SAX event to the current element so that they are available
	 * to child elements and attributes
	 * 
	 * @param element DOCUMENT ME!
	 */
	protected void addDeclaredNamespaces(Element element) {
		Namespace elementNamespace = element.getNamespace();

		for (int size = namespaceStack.size(); declaredNamespaceIndex < size; declaredNamespaceIndex++) {
			Namespace namespace = namespaceStack.getNamespace(declaredNamespaceIndex);

			// if ( namespace != elementNamespace ) {
			element.add(namespace);

			// }
		}
	}

	/**
	 * Add all the attributes to the given elements
	 * 
	 * @param element DOCUMENT ME!
	 * @param attributes DOCUMENT ME!
	 */
	protected void addAttributes(Element element, Attributes attributes) {
		// XXXX: as an optimisation, we could deduce this value from the current
		// SAX parser settings, the SAX namespaces-prefixes feature
		boolean noNamespaceAttributes = false;

		if (element instanceof AbstractElement) {
			// optimised method
			AbstractElement baseElement = (AbstractElement) element;
			List<Attribute> attributeList = baseElement
					.setAttributes(attributes, namespaceStack, noNamespaceAttributes);
			for (Attribute attribute : attributeList) {
				setAttributeLocation(element, attribute);
			}
		}
		else {
			int size = attributes.getLength();

			for (int i = 0; i < size; i++) {
				String attributeQName = attributes.getQName(i);

				if (noNamespaceAttributes || !attributeQName.startsWith("xmlns")) {
					String attributeURI = attributes.getURI(i);
					String attributeLocalName = attributes.getLocalName(i);
					String attributeValue = attributes.getValue(i);

					QName qName = namespaceStack.getAttributeQName(attributeURI, attributeLocalName, attributeQName);
					Attribute attribute = element.addAttribute(qName, attributeValue);
					setAttributeLocation(element, attribute);
				}
			}
		}
	}

	/**
	 * Adds an internal DTD declaration to the list of declarations
	 * 
	 * @param declaration DOCUMENT ME!
	 */
	protected void addDTDDeclaration(Object declaration) {
		if (internalDTDDeclarations == null) {
			internalDTDDeclarations = new ArrayList();
		}

		internalDTDDeclarations.add(declaration);
	}

	/**
	 * Adds an external DTD declaration to the list of declarations
	 * 
	 * @param declaration DOCUMENT ME!
	 */
	protected void addExternalDTDDeclaration(Object declaration) {
		if (externalDTDDeclarations == null) {
			externalDTDDeclarations = new ArrayList();
		}

		externalDTDDeclarations.add(declaration);
	}

	protected ElementStack createElementStack() {
		return new ElementStack();
	}

	//
	// location methods
	//

	/**
	 * Set the document start location, the default value is <code>(1, 1)</code>
	 * 
	 * @param document
	 */
	protected void setDocumentStartLocation(Document document) {
		document.setStartLocation(Location.newInstance(locator));
	}

	/**
	 * Set the processing instructiong location, the default value is the end of this pi
	 * 
	 * @param pi
	 */
	protected void sestProcessingInstructionLocation(ProcessingInstruction pi) {
		Location loc = Location.newInstance(locator);
		pi.setStartLocation(loc);
		pi.setEndLocation(loc);
	}

	/**
	 * Set the DTD declaration start location, the default value is the first location of &quot;[&quot; or
	 * &quot;&gt;&quot;
	 * 
	 * @param docType
	 */
	protected void setDTDStartLocation(DocumentType docType) {
		docType.setStartLocation(Location.newInstance(locator));
	}

	protected void setElementDeclLocation(ElementDecl elementDecl) {
		Location loc = Location.newInstance(locator);
		elementDecl.setStartLocation(loc);
		elementDecl.setEndLocation(loc);
	}

	protected void setAttributeDeclLocation(AttributeDecl attributeDecl) {
		Location loc = Location.newInstance(locator);
		attributeDecl.setStartLocation(loc);
		attributeDecl.setEndLocation(loc);
	}

	/**
	 * Set the internal entity declaration location, the default value is the end of this entity declaration, that says
	 * the lcation after the sign &quot;&gt;&quot;
	 * 
	 * @param entityDecl
	 */
	protected void setInternalEntityDeclLocation(InternalEntityDecl entityDecl) {
		Location loc = Location.newInstance(locator);
		entityDecl.setStartLocation(loc);
		entityDecl.setEndLocation(loc);
	}

	protected void setExternalEntityDeclLocation(ExternalEntityDecl entityDecl) {
		Location loc = Location.newInstance(locator);
		entityDecl.setStartLocation(loc);
		entityDecl.setEndLocation(loc);
	}

	/**
	 * Set the DTD declaration end location, the default value is meanless becasue the location is the end of the
	 * external dtd file
	 * 
	 * @param docType
	 */
	protected void setDTDEndLocation(DocumentType docType) {
		docType.setEndLocation(Location.newInstance(locator));
	}

	/**
	 * Set the element start location, the default value is the end of this <b>tag begin</b>, e.g. &lt;someTag&gt;,
	 * declaration sign &quot;&gt;&quot;
	 * 
	 * @param element
	 */
	protected void setElementStartLocation(Element element) {
		element.setStartLocation(Location.newInstance(locator));
	}

	/**
	 * Set the attribute location, the default value is same as the parent element location
	 * 
	 * @param parentElement
	 * @param attribute
	 */
	protected void setAttributeLocation(Element parentElement, Attribute attribute) {
		Location loc = Location.newInstance(locator);
		attribute.setStartLocation(loc);
		attribute.setEndLocation(loc);
	}

	/**
	 * Set the element end location, the default value is the end of this <b>tag end</b>, e.g. &lt;/someTag&gt;,
	 * declaration sigh &quot;&gt;&quot;
	 * 
	 * @param element
	 */
	protected void setElementEndLocation(Element element) {
		element.setEndLocation(Location.newInstance(locator));
	}

	/**
	 * Set the comment location, the default value is the end of this comment tag sign &quot;&gt;&quot;
	 * 
	 * @param comment
	 */
	protected void setCommentLocation(Comment comment) {
		Location loc = Location.newInstance(locator);
		comment.setStartLocation(loc);
		comment.setEndLocation(loc);
	}

	protected void setEntityLocation(Entity entity) {
		Location loc = Location.newInstance(locator);
		entity.setStartLocation(loc);
		entity.setEndLocation(loc);
	}

	protected void setTextLocation(Text text) {
		Location loc = Location.newInstance(locator);
		text.setStartLocation(loc);
		text.setEndLocation(loc);
	}

	protected void setDocumentEndLocation(Document document) {
		document.setEndLocation(Location.newInstance(locator));
	}

}

/*
 * Redistribution and use of this software and associated documentation ("Software"), with or without modification, are
 * permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions must also contain a
 * copy of this document.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 * following disclaimer in the documentation and/or other materials provided with the distribution.
 * 
 * 3. The name "DOM4J" must not be used to endorse or promote products derived from this Software without prior written
 * permission of MetaStuff, Ltd. For written permission, please contact dom4j-info@metastuff.com.
 * 
 * 4. Products derived from this Software may not be called "DOM4J" nor may "DOM4J" appear in their names without prior
 * written permission of MetaStuff, Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
 * 
 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
 * 
 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 */
